Genesis 3D

 

per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto!

Corso sull'engine 3D di Genesis

Dodicesima lezione

di Luca Sabatucci

Per commentare, dare suggerimenti o consigli (molto apprezzati) e per segnalare eventuali errori o
disattenzioni (sempre possibili), puoi inviare una email all'autore (clicca sul nome sopra). Grazie per la collaborazione!


Programmazione parte III

In questo tutorial vedremo come:

1) prelevare dalla mappa le entity

2) interagire con l'ambiente

3) inserire un actor

4) inserire un suono localizzato

Le entity

Quando abbiamo creato la mappa del nostro esempio, abbiamo aggiunto alcuni oggetti fittizi, chiamate entità. Una entità contiene una serie di informazioni utili al programmatore per posizionare oggetti nello spazio di stato, e per aggiungere effetti molto interessanti, in modo quasi automatico.

 

Ecco cosa dice la documentazione ufficiale a proposito:

The entity subsystem allows you to get design time data from a level that was edited with GEDIT. The data is streamed in from the level file so that the programmer is not required to do any post editing, runtime linking of structure elements.

Si tratta di un metodo per linkare alla mappa dell'ambiente alcune informazioni che poi possono essere lette in fase di esecuzione.

 

Alcune delle entità "standard" sono la DeathMatchStart, Door, Light, DinamicLight, Corona ecc... Vedremo che è possibile usare anche delle entità "user defined".

 

I comandi per leggere le entità sono essenzialmente 3:

 

geEntity_EntitySet geWorld_GetEntitySet(geWorld *World, const char *SetName)

 

Dato il puntatore dell'oggetto geWorld chiediamo di estrarre una lista di entità di un certo tipo. SetName indica il nome della categoria di entità che vogliamo estrarre.

 

geEntity *geEntity_EntitySetGetNextEntity(geEntity_EntitySet *EntitySet, geEntity *Entity)

 

Mediante questa funzione si scorre la lista di entity. EntitySet è il valore fornito dalla funzione geWorld_GetEntitySet. Mentre Entity è un puntatore ad una entity. Il valore restituito sarà l'entity successiva a quella indicata. se Entity è NULL viene restituita la prima Entity nella lista. Se non ci sono più entity viene restituito NULL.

 

void *geEntity_GetUserData(geEntity *Entity)

 

Questa funzione si occupa di prelevare le informazioni relative all'entità. Poiché viene restituito un puntatore generico (void *) per poter assegnare l'area di memoria puntata ad un puntatore della struttura dati opportuna è necessario effettuare un cast esplicito. Vedremo un esempio tra breve.

La funzione GetStartingPosition()

Con questa funzione rileviamo l'informazione sulla posizione iniziale del player. L'entità che andiamo a interrogare è DeathMatchStart, la cui definizione si trova nel file entity.h ed è:

 

// CameraStartPosition -------------

#pragma GE_Type("Player.ico")

typedef struct weOrigineCamera

{

#pragma GE_Published

    geVec3d     Origin;

#pragma GE_Origin(Origin)

}   DeathMatchStart;

 

La spiegazione dei comandi #pragma li vedremo dopo. Per adesso notiamo che l'unico campo è Origin, un vettore nello spazio di stato.

Vediamo la funzione:

 

geBoolean GetStartingPosition(geVec3d *posizione)

{

      geEntity_EntitySet *lEntitySet;

      geEntity *lEntity;

      DeathMatchStart *camera;

      geBoolean lettura = GE_FALSE;

 

      lEntitySet = geWorld_GetEntitySet(World, "DeathMatchStart");

 

Viene prelevata la lista di entity di tipo "DeathMatchStart", quindi si preleva il primo elemento della lista.

 

      if (lEntitySet) {

            lEntity = geEntity_EntitySetGetNextEntity(lEntitySet, NULL); // Get the first DeathMatchStart

           

            if (lEntity) {

                  camera = (DeathMatchStart *) geEntity_GetUserData(lEntity); // Get the UserActor from the entity

                  *posizione = camera->Origin;

                  lettura = GE_TRUE;

            }

      }

 

      return lettura;

}

 

Tramite una conversione esplicita a puntatore (DeathMatchStart *) possiamo accedere al campo dati Origin che leggiamo e forniamo come parametro di ritorno della funzione.

Modifichiamo l'entità Door

Come ci suggerisce il nome stesso l'entità Door serve per animare la porte di un ambiente. La definizione standard delle Door è la seguente:

 

// Door

#pragma GE_Type("Model.ico")

typedef struct  Door

{

#pragma GE_Published

    geWorld_Model   *Model;

    geVec3d             Origin;

#pragma GE_Origin(Origin)

}   Door;

 

Gli unici campi sono Model, un puntatore ad un modello dell'ambiente e Origin che indica la posizione della porta.

Per dimostrare l'estendibilità del sistema, proviamo ad inserire una serie di informazioni supplementari.

 

// Door ------------------------

#pragma GE_Type("Model.ico")

typedef struct  weDoor

{

#pragma GE_Published

      float             DistanceToOpen;   // Max Distance person has to be away from the door to open it.

      geBoolean         Automatic;        // Indica se la porta si apre automaticamente

      geBoolean         Locked;           // Indica se la porta può essere aperta

 

      geWorld_Model   *Model;

    geVec3d             Origin;

 

#pragma GE_Origin(Origin)

 

#pragma GE_DefaultValue(DistanceToOpen, "200.0")

#pragma GE_DefaultValue(Automatic, "True")

#pragma GE_DefaultValue(Locked, "False")

}   ExtDoor;

////////////////////////////////

 

Aggiungiamo quindi una variabile "DistanceToOpen" che indica che se la porta è automatica, qual'è la distanza alla quale avvicinandoci la porta si apre.

Altro campo è "Automatic" che serve a determinare se la porta è automatica, oppure se per aprirla dobbiamo interagire con essa.

Locked indica invece se la porta è aperta o chiusa a chiave. Potremmo anche indicare un numero ID della chiave che apre questa porta ma le possibilità di espansione sono limitate solo dalla fantasia del programmatore.

Vediamo quindi anche il significato delle direttive #pragma.

 

#pragma GE_Type("Model.ico"): serve a specificare l'icona da usare nel world editor per indicare tale entity

 

#pragma GE_Published: serve ad indicare quali sono i campi da visualizzare nell'entity editor, ovvero i campi manipolabili direttamente nell'editor.

 

#pragma GE_Origin(Origin): questo comando molto utile serve per assegnare automaticamente ad un campo (nel nostro caso origin) il valore del vettore dell'entity. Un modo comodo per settare punti nello spazio 3d.

 

#pragma GE_DefaultValue(DistanceToOpen, "200.0"): alcuni valori possono essere settati a dei valori standard.

 

#pragma GE_Documentation(Automatic, "spedifica il tipo di porta"): aggiunge la spiegazione del campo dati nell'entity editor.

 

Adesso che abbiamo creato la nostra entity dobbiamo informare il World Editor della posizione in cui leggere il file .H nella quale essa si trova. Questo può essere fatto andando a modificare le opzioni di livello, nel campo Headers. Bisogna stare attenti che il programma non gradisce una definizione multipla. Assicurarsi quindi che il file in cui si trova la DeathMatchStart non sia nella stessa directory dell'enity che vogliamo far leggere. Oppure dobbiamo togliere il riferimento alla directory con le definizioni standard (operazione non consigliata).

 

Possiamo quindi procedere creando una directory "Entity" all'interno della quale mettere un file .H che contiene tale descrizione.

Adesso agendo con il World Editor aggiungere una enitity di tipo ExtDoor (il tipo che abbiamo appena creato) e modificare i campi secondo fantasia, oppure lasciare inalterato il valore di default. Quindi compilare il livello.

La funzione GetDoorInfo()

Prima di capire come funziona questa funzione ecco la struttura dati usata per realizzare una lista. Ogni porta presente nel livello viene memorizzata in un nodo di tale lista. In questo modo poi per effettuare l'update di ogni porta basta scorrere l'elenco.

 

typedef struct  NodoPorta

{

      float             CurrentPos;       // Time currently at in animation.

      float             TimeAllowedOpen;// Time which the door is allowed to be open for.

      float             DistanceToOpen;   // Max Distance person has to be away from the door to open it.

      geBoolean         Automatic;        // Indica se la porta si apre automaticamente

      geBoolean         Locked;                 // Indica se la porta può essere aperta

 

      geWorld_Model     *Model;

      geVec3d           Origin;

 

      geBoolean         moving;

 

      struct NodoPorta  *prossimo;

 

} NodoPorta;

 

NodoPorta *lista_porta = NULL;

 

La maggior parte dei campi sono quelli della struttura ExtDoor che abbiamo creato. Altri campi come CurrentPos, moving e prossimo servono per la gestione dell'animazione e della lista.

 

void GetDoorInfo() {

      geEntity_EntitySet *Set=NULL; // conterra' info sull'insieme delle entita' door

      geEntity *Entity=NULL;             // conterra' info su una entita'

      NodoPorta *corrente = NULL;

      Door *temp;

 

      Set = geWorld_GetEntitySet(World, "ExtDoor");

 

Viene prelevato l'EntitySet relativa all'entità che abbiamo definito noi. Mediante un ciclo while scorriamo la lista di entity e preleviamo i dati relativi.

 

      if (Set) {

            Entity = geEntity_EntitySetGetNextEntity(Set,NULL);

            while (Entity) {

                  temp = (Door*) geEntity_GetUserData(Entity);

 

                  if (corrente == NULL) {

                        lista_porta = malloc(sizeof(NodoPorta));

                        corrente = lista_porta;

                  } else {

                        corrente->prossimo = malloc(sizeof(NodoPorta));

                        corrente = corrente->prossimo;

                  }

 

Quindi aggiungiamo un nodo alla nostra lista di porte e modifichiamo i campi di tale nodo in modo che rispecchiano i valori letti nella entity.

 

                  corrente->Automatic          = temp->Automatic;

                  corrente->DistanceToOpen     = temp->DistanceToOpen;

                  corrente->Locked             = temp->Locked;

                  corrente->Model              = temp->Model;

                  corrente->Origin             = temp->Origin;

 

                  corrente->moving = GE_FALSE;

                  corrente->CurrentPos = 0.0f;

 

                  corrente->prossimo = NULL;

 

                  Entity = geEntity_EntitySetGetNextEntity(Set,Entity);

            }

      }

}

 

Dopo aver richiamato tale funzione avremo una lista di porte, il cui primo nodo è puntato da lista_porta.

La funzione UpdateDoor(float speed)

Adesso che abbiamo letto le informazioni relative alle porte presenti nell'ambiente è necessario richiamare ad ogni loop nel MainLoop la funzione che aggiorna la posizione delle varie porte prima del rendering.

Il parametro speed indica la velocità di apertura delle porte.

 

void UpdateDoor(float speed) {

      NodoPorta *corrente = lista_porta;

      geMotion *Motion;

      gePath   *Path;

      geXForm3d Dest;         // questa matrice conterra' la matrice di trasformazione per la porta

     

      float dist;

      float tStart,tEnd;

 

      while (corrente) {

 

            dist = geVec3d_DistanceBetween(&pl.posizione,&corrente->Origin);

 

Per prima cosa scorriamo la lista. Per ogni nodo rileviamo la distanza tra la posizione del giocatore e quella della porta. La distanza ci serve per le porte automatiche. Se la distanza è inferiore a quella fissata dal parametro DistanceToOpen la porta deve essere aperta. Per le porte non automatiche invece viene controllato il tasto SHIFT.

 

            // fase 1 verifica se la porta e' vicina al giocatore

            if ((!corrente->Locked) && (corrente->Automatic || io.keys[VK_SHIFT]))

                  if (dist <= corrente->DistanceToOpen) {

 

                        corrente->moving = GE_TRUE;

                  }

 

Se la porta deve essere aperta, o se si sta già aprendo, dobbiamo usare il comando geWorld_OpenModel che serve per comunicare all'engine che durante il rendering il modello "porta" potrebbe mostrare ciò che sta dietro.

 

            // aggiorna il movimento della porta

            if (corrente->moving) {

                  // informa l'engine di mostrare quello che sta dietro

                  geWorld_OpenModel(World, corrente->Model, GE_TRUE);

 

                  Motion = geWorld_ModelGetMotion(corrente->Model);

                  Path = geMotion_GetPath(Motion, 0);

 

Adesso vengono prelevati gli oggetti geMotion e gePath. Spieghiamo brevemente di che si tratta.

Ecco cosa dice la documentazione ufficiale a proposito di geMotion

 

The Motion module provides support for creating and maintaining lists of named Paths and associated time-indexed events, and methods to sample the animation paths at any arbitrary time.

 

Mentre per quanto riguarda gePath:

 

The Path module provides support for creating and maintaining time indexed keyframe data, and sampling the path at arbitrary times.

 

In pratica una animazione abbiamo visto che è formata una serie di matrici di trasformazioni, associate ad un parametro temporale chiamato keyframe. I keyframe non sono necessariamente contigui tra di loro. Per conoscere la posizione intermedia tra due keyframe si agisce per interpolazione. Questa operazione è svolta dall'oggetto gePath.

L'oggetto geMotion permette di associare ad un oggetto 3d (sia esso un modello o un actor) una lista di gePath, i quali possono essere usati indipendentemente, oppure mixati assieme per ottenere animazioni più complesse.

Nel nostro caso è presente un'unica animazione, per cui non abbiamo molte alternative.

 

                  corrente->CurrentPos += speed;

 

                  geMotion_GetTimeExtents(Motion, &tStart, &tEnd);

 

Questa funzione, geMotion_GetTimeExtents, fornisce il valore temporale associato al primo e all'ultimo keyframe, ovvero le estensioni temporali dell'animazione stessa.

 

                  if (corrente->CurrentPos >= tEnd) {

                        corrente->CurrentPos = 0.0f;

                        corrente->moving = GE_FALSE;

                  } else {

                        gePath_Sample(Path, corrente->CurrentPos, &Dest);

                        geWorld_SetModelXForm(World, corrente->Model, &Dest);

                  }

            }

 

            corrente = corrente->prossimo;

      }

}

 

Quindi effettuiamo il controllo dell'animazione. Se il frame corrente si trova entro start ed end allora l'animazione deve essere effettuata. Altrimenti vuol dire che è finita.

Per effettuare l'animazione dobbiamo estrarre la matrice di trasformazione opportuna mediante gePath_Sample ed applicarla al modello mediante geWorld_SetModelXForm.

Gli actor

La documentazione relativa agli actor si trova inserita nel file actor.h; ecco riportata una parte:

 

/*   Actor

           

            This object is designed to support character animation.

            There are two basic objects to deal with. 

           

            Actor Definition (geActor_Def)

                        A geActor_Def embodies the geometry (polygon, and bone information),

                        and a library of motions that can be applied to that geometry.

 

            Actor

                        A geActor is an instance of an actor definition.  The definition is used for

                        the geometry, but all additional settings, such as the bone pose, lighting information,

                        and cuing information is unique for a geActor.

 

Gli actor sono stati pensati per realizzare animazione di oggetti nella nostra scena. Ci sono due oggetti con i quali interagire: i geActor_Def che contengono la descrizione della geometria dell'actor (poligoni, bones e motions) e i geActor che rappresentano delle istanze di un geActor_Def. Ogni geActor condivide le informazioni sulla geometria mentre le informazioni sulle bones, sull'illuminazione e su altri controlli sono unici.

 

Il grande vantaggio dell'uso degli actor sta infatti nella possibilità di definire delle animazioni molto precise su una struttura gerarchica. Gli actor sono memorizzati in file esterni in formato ACT nella quale è contenuta sia l'informazione sulla geometria che sull'animazione.

 

In questo tutorial vedremo come caricare un actor e animarlo.

La funzione LoadActor()

Allo scopo di gestire l'animazione di un actor creiamo una struttura dati che chiamiamo Gimnic, in quanto il nostro actor è una donna che fa ginnastica.

 

typedef struct {

      geActor *actor;

      geVec3d posizione;

      geVec3d angolo;

     

      int animation;

      float CurrentPos;

 

      int coda[4];

 

} Gimnic;

 

Gimnic gimnic;

 

Il primo campo è un puntatore all'oggetto geActor corrispondente. I seguenti due campi contengono informazioni sulla posizione e sulla angolazione dell'actor rispetto all'ambiente.

Il campo animation serve per selezionare quale animazione è attualmente in corso, mentre CurrentPos contiene il numero di frame corrente per l'animazione stessa.

L'array coda è un modo semplice per memorizzare ripetizioni di animazione in sequenza. Ogni cella dell'array contiene un numero che corrisponde all'animazione in corso. La variabile animation è l'indice di tale array.

 

void LoadGimnic() {

      geVFile *ActorFile = NULL;

      geActor_Def *ActorDef = NULL;

 

      gimnic.actor = NULL;

     

      //load the act file.

      ActorFile = geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "Actor\\gimnic.act", NULL, GE_VFILE_OPEN_READONLY);

 

      if(ActorFile)

            {

              //create a definition of the actor

              ActorDef = geActor_DefCreateFromFile (ActorFile);

 

La funzione geActor_DefCreateFromFile svolge il ruolo di caricare il file ACT e di memorizzare le informazioni in un oggetto geActor_Def, che ci servirà da stampo per creare i nostri Actor.

 

              if(ActorDef)

                  {

                        gimnic.actor = geActor_Create (ActorDef);

 

Con la funzione geActor_Create istanziamo un actor in base alla sua definizione e lo memorizziamo nella nostra struttura dati.

                                              

                        //add that actor to the world

                        geWorld_AddActor (World, gimnic.actor, GE_ACTOR_RENDER_NORMAL | GE_ACTOR_COLLIDE, 0xffffffff);

 

Adesso non ci resta che informare il geWorld che vogliamo usare questo actor. Sarà esso poi a gestire il rendering opportunamente.

 

                        //make the actor bigger.

                    geActor_SetScale(gimnic.actor, 4.0f,4.0f,4.0f);

 

Adesso possiamo effettuare alcune semplici operazioni sull'actor, come ad esempio effettuare una operazione di scala. Nella funzione SetupActor effettuiamo altre inizializzazioni.

 

                  }

            }

 

      geVFile_Close(ActorFile); // Close our file.

 

      SetupActor();

}

 

La funzione SetupActor()

Lo scopo di tale funzione è chiaramente quello di preparare il nostro actor affinché venga visualizzato correttamente nell'ambiente.

 

void SetupActor() {

      geXForm3d XForm;

 

      if (gimnic.actor) {

 

            gimnic.coda[0]=1;

            gimnic.coda[1]=2;

            gimnic.coda[2]=2;

            gimnic.coda[3]=1;

            gimnic.animation = 0;

            gimnic.CurrentPos = 0.0f;

 

Impostiamo la coda delle animazioni in modo che vengano svolte in sequenza le animazioni 1,2,2,1 in un loop continuo.

 

            gimnic.angolo.X = 4.71f;     // correzzione di -90 gradi

            gimnic.angolo.Y = 0.0f;

            gimnic.angolo.Y = 0.0f;

 

            gimnic.posizione.X = 0;//-100;

            gimnic.posizione.Y = -150;

            gimnic.posizione.Z = 0;//600;

 

Posizioniamo l'actor nell'ambiente e lo ruotiamo. E' chiaro che anziché utilizzare dei valori sperimentali, si potrebbe demandare alle entity il posizionamento dell'actor nell'ambiente.

 

            geXForm3d_SetIdentity(&XForm);

 

            //Setup the rotation

            geXForm3d_RotateX(&XForm, gimnic.angolo.X);

            geXForm3d_RotateY(&XForm, gimnic.angolo.Y);

            geXForm3d_RotateZ(&XForm, gimnic.angolo.Z);

 

            geXForm3d_Translate(&XForm, gimnic.posizione.X, gimnic.posizione.Y,gimnic.posizione.Z);

 

            geActor_ClearPose(gimnic.actor, &XForm); // Posiziona

      }

}

 

Infine impostiamo una matrice di trasformazione e la usiamo per posizionare l'actor mediante il comando geActor_ClearPose.

 

Queste due funzioni devono essere richiamate in fase di inizializzazione, mentre la terza funzione, UpdateActor viene usata dentro la MainLoop.

La funzione MainLoop()

Questa funzione ha il compito di aggiornare la posizione e il keyframe corrente per l'actor. Anche qui il parametro speed serve per rendere più o meno veloce l'animazione.

 

void UpdateActor(geFloat speed) {

      geMotion *actor_motion = NULL;

      geActor_Def *actor_def = NULL;

      geXForm3d XForm;

 

      geFloat start = 0.0f, end = 0.0F;

 

      actor_def = geActor_GetActorDef(gimnic.actor);

     

      switch (gimnic.coda[gimnic.animation]) {

      case 1:

            actor_motion = geActor_GetMotionByName(actor_def, "piegamenti_ginocchia");

            break;

      case 2:

            actor_motion = geActor_GetMotionByName(actor_def, "saltello_sul_posto");

            break;

      }

 

A seconda dell'animazione impostata nella coda, si preleva il geMotion relativo all'animazione corrente. Il comando per effettuare tale operazione è geActor_GetMotionByName. Il nome si riferisce a quello impostato nell'actor builder in fase di costruzione.

 

      if (actor_motion) {

            geMotion_GetTimeExtents(actor_motion, &start, &end);

 

            geXForm3d_SetIdentity(&XForm);

 

            //Setup the rotation

            geXForm3d_RotateX(&XForm, gimnic.angolo.X);

            geXForm3d_RotateY(&XForm, gimnic.angolo.Y);

            geXForm3d_RotateZ(&XForm, gimnic.angolo.Z);

 

            geXForm3d_Translate(&XForm, gimnic.posizione.X, gimnic.posizione.Y,gimnic.posizione.Z);

 

            geActor_ClearPose(gimnic.actor, &XForm); // Posiziona

 

Per prima cosa viene posizionato l'actor in considerazione alla sua posizione e al suo orientamento.

 

            geActor_SetPose(gimnic.actor, actor_motion, gimnic.CurrentPos, NULL);

 

Quindi viene impostato il keyframe CurrentPos relativo alla nostra animazione. Sarà l'oggetto geActor stesso a occuparsi di effettuare le trasformazioni relative delle singole parti di cui è costituito, eventualmente interpolando la matrice di trasformazione.

 

            gimnic.CurrentPos += speed;

           

            //geXForm3d_Translate(&(motore->box->posizione),0,0,1.0);

            if (gimnic.CurrentPos > end) {

                  gimnic.animation++;

                  if (gimnic.animation > 3)

                        gimnic.animation = 0;

                  gimnic.CurrentPos = start;

            }

           

      }

 

}    

 

Questa ultima parte di codice serve a gestire la coda in maniera che finita una animazione si inizi l'animazione successiva in coda e poi si ricominci da capo.

Il sistema sonoro

Anche genesis ha un proprio sistema sonoro che si "aggancia" a quello del sistema operativo. Affinché si possano eseguire suoni è necessario inizializzare tale sistema. Questo deve essere fatto nella funzione InitEngine che riporto per comodità di seguito. In blu l'intera funzione, in marrone le aggiunte.

 

geBoolean InitEngine(HWND hWnd)

{

      GE_Rect     Rect;             //For the camera object's rectangular view

      geDriver_System *DrvSys;

 

      Engine = geEngine_Create(hWnd, "MinApp", ".");

      if (!Engine)

      {

            MessageBox(hWnd, "Could not create engine object!", "MinApp Error...", 48);

            return GE_FALSE;

      }

 

      geEngine_EnableFrameRateCounter(Engine, FALSE);

     

      //This function will enable you to use sound with your program through the Genesis API.

      SoundSys = geSound_CreateSoundSystem(hWnd);

      if (!SoundSys)

            MessageBox(hWnd, "Could not create Sound System!  There will be no sound!", "No Sound...", 48);

     

      DrvSys = geEngine_GetDriverSystem(Engine);

      if (!DrvSys)

      {

            MessageBox(hWnd, "Could not get the Genesis Driver System!", "MinApp Error...", 48);

            return GE_FALSE;

      }

     

      //Set the driver you want to use

      FindDriver(DrvSys);

            .

            .

            .

 

Come si può vedere si è creato un nuovo oggetto chiamato geSound_System. Tale variabile deve essere dichiarata, insieme ad altre tre nella main.h

 

geSound_System          *SoundSys;        //The Genesis Sound System

geSound_Def             *bandaDEF;

geSound                 *bandaSOUND;

geVec3d                 posizione_suono;

 

Adesso analizziamo il modo in cui usare tale sistema.

La funzione CaricaSuono()

Questa funzione si occupa di caricare un file .wav e di memorizzarne l'handler in un puntatore che chiamiamo bandaDEF. Questo handler contiene la descrizione di ciò che deve essere suonato.

 

void CaricaSuono() {

      geVFile *SoundFile = NULL;

 

      SoundFile = geVFile_OpenNewSystem(NULL,

                             GE_VFILE_TYPE_DOS,

                             "wav\\banda.wav",

                             NULL,

                             GE_VFILE_OPEN_READONLY);

           

      if (SoundFile) {

            bandaDEF = geSound_LoadSoundDef(SoundSys,SoundFile);

 

Mediante la funzione geSound_LoadSoundDef si carica nell'handler il file wav desiderato.

 

            posizione_suono.X = 0;

            posizione_suono.Y = 0;

            posizione_suono.Z = 0;

 

In particolare, poiché stiamo cercando un effetto abbastanza sofisticato, forniamo una sorgente del suono, come se questo fosse localizzato nel punto assegnato.

 

            geSound_SetMasterVolume(SoundSys, 0.9f);

 

Assegniamo inoltre un volume di riproduzione del suono. Si noti che ancora nessun suono sta per essere riprodotto. Il valore del volume è un valore percentuale compreso tra 0.0 e 1.0 del volume totale.

 

      } else

            bandaDEF = NULL;

 

}

La funzione RiproduciSuono()

Quello che abbiamo fatto fin ora (e si può notare una matrice comune per molte delle operazioni svolte) è di preparare l'oggetto suono affinché possa essere riprodotto. con questa funzione, che richiameremo ad ogni ciclo del MainLoop, iniziamo la riproduzione.

 

void PlaySound__() {

      geXForm3d XForm;

      geFloat volume, pan, frequency;

 

      geXForm3d_SetIdentity(&XForm);

 

      //determina le caratteristiche del suono in base alla posizione della camera

      geXForm3d_RotateX(&XForm, pl.angolo.X);

      geXForm3d_RotateY(&XForm, pl.angolo.Y);

      geXForm3d_RotateZ(&XForm, pl.angolo.Z);

 

      geXForm3d_Translate(&XForm, pl.posizione.X, pl.posizione.Y+pl.height, pl.posizione.Z);

 

      geSound3D_GetConfig(World,&XForm,&posizione_suono,500.0f,2.0f,&volume,&pan,&frequency);

 

Con questa operazione indichiamo al sistema sonoro di calcolare volume, frequenza e pan relativi al suono tenendo conto della attenuazione dovuta alla distanza del player dalla sorgente sonora.

           

      if (!geSound_SoundIsPlaying(SoundSys, bandaSOUND) ) {

            bandaSOUND = geSound_PlaySoundDef(SoundSys,bandaDEF,volume,pan,frequency,FALSE);

      } else {

            geSound_ModifySound(SoundSys, bandaSOUND, volume, pan, frequency);

      }

}

 

Adesso facciamo una differenziazione. Se il suono è già in riproduzione (ovvero se geSound_SoundIsPlaying(SoundSys, bandaSOUND) restituisce true modifichiamo i parametri di volume, pan e frequenza, in modo che si percepisca una attenuazione con la distanza. se invece il suono non è ancora stato riprodotto, o è terminato, allora viene inizializzata la riproduzione mediante il comando ge_Sound_PlaySoundDef. La variabile geSound tiene conto dell'attuale riproduzione del suono. C'è quindi la stessa differenza tra geSound e geSound_Def che c'è tra geActor e geActor_Def.

Le funzioni WinMain() e MainLoop()

Vediamo infine come sono state modificate le due funzioni WinMain() e MainLoop() in seguito a queste aggiunte:

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {

      InitLog();

      LoadPrefs(io.driver, &io.Width, &io.Height);

 

      CreateMainWindow(hInstance,nShowCmd);

     

      //Initialize the Genesis engine

      InitEngine(hWnd);

      Setup();

 

      GetDoorInfo();

      LoadActor();

      CaricaSuono();

     

      MainLoop();

 

      //Shutdown the Genesis engine and clean up the memory     

      Shutdown();

     

      //End Program

      return 1;

}

 

void MainLoop() {

      MSG                     msg;

      int                     run;

      geBoolean               coll;

 

      //Main game loop

      run = 1;

 

      while (run)

      {

            if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {    // Is There A Message Waiting?

                  if (msg.message==WM_QUIT) {                    // Have We Received A Quit Message?

                        run = 0;                                       // If So done=TRUE

                  } else {                                             // If Not, Deal With Window Messages

                        TranslateMessage(&msg);                  // Translate The Message

                        DispatchMessage(&msg);                   // Dispatch The Message

                  }

            } else {                                                   // If There Are No Messages

 

                  if (pl.stato == standing || pl.stato == falling) {

                        coll = Gravity((float)pl.caduta);

                        if (coll == GE_TRUE)

                             pl.stato = standing;

                  }

 

                  // Aggiorna l'ambiente

                  MoveCamera();

                  AggiornaXForm();  // sposta la camera

 

                  UpdateDoor(0.01f);

                  UpdateActor();

                  RiproduciSuono();

 

                  if (!geEngine_BeginFrame(Engine, Camera, GE_FALSE))

                        run = 0;

 

                  if (!geEngine_RenderWorld(Engine, World, Camera, 0.0f))

                        run = 0;

                 

                  if (!geEngine_EndFrame(Engine))

                        run = 0;

 

                  if (io.keys[VK_ESCAPE]) {                                 

                        run=0;

                  }

            }

      }

}

 

Con questo tutorial termina la carrellata sull'interessanti possibilità messe a disposizione da genesis. Cose di cui parlare ce ne sarebbero ancora molte. Ma si tratta di argomenti avanzati che meritano ancora un certo chiarimento anche nei confronti del sottoscritto. In ogni caso già così si sono fornite le basi per realizzare delle animazioni 3d molto belle e sofisticate.

 


Questo articolo è stato scaricato dal Club di informatica
Pagina curata da:
Luca Sabatucci